import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APIMessage, MessageFlags } from 'discord-api-types/v10';
import { Structure } from '../Structure.js';
import { MessageFlagsBitField } from '../bitfields/MessageFlagsBitField.js';
import { dateToDiscordISOTimestamp } from '../utils/optimization.js';
import { kData, kEditedTimestamp } from '../utils/symbols.js';
import { isIdSet } from '../utils/type-guards.js';
import type { Partialize } from '../utils/types.js';

// TODO: missing substructures: application

/**
 * Represents a message on Discord.
 *
 * @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate`
 * @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it
 */
export class Message<Omitted extends keyof APIMessage | '' = 'edited_timestamp' | 'timestamp'> extends Structure<
	APIMessage,
	Omitted
> {
	/**
	 * The template used for removing data from the raw data stored for each Message
	 */
	public static override DataTemplate: Partial<APIMessage> = {
		set timestamp(_: string) {},
		set edited_timestamp(_: string) {},
	};

	protected [kEditedTimestamp]: number | null = null;

	/**
	 * @param data - The raw data received from the API for the message
	 */
	public constructor(data: Partialize<APIMessage, Omitted>) {
		super(data);
		this.optimizeData(data);
	}

	/**
	 * {@inheritDoc Structure.optimizeData}
	 *
	 * @internal
	 */
	protected override optimizeData(data: Partial<APIMessage>) {
		if (data.edited_timestamp) {
			this[kEditedTimestamp] = Date.parse(data.edited_timestamp);
		}
	}

	/**
	 * The message's id
	 */
	public get id() {
		return this[kData].id;
	}

	/**
	 * The id of the interaction's application, if this message is a reply to an interaction
	 */
	public get applicationId() {
		return this[kData].application_id;
	}

	/**
	 * The channel's id this message was sent in
	 */
	public get channelId() {
		return this[kData].channel_id;
	}

	/**
	 * The timestamp this message was created at
	 */
	public get createdTimestamp() {
		return isIdSet(this.id) ? DiscordSnowflake.timestampFrom(this.id) : null;
	}

	/**
	 * The time the message was created at
	 */
	public get createdAt() {
		const createdTimestamp = this.createdTimestamp;
		return createdTimestamp ? new Date(createdTimestamp) : null;
	}

	/**
	 * The content of the message
	 */
	public get content() {
		return this[kData].content;
	}

	/**
	 * The timestamp this message was last edited at, or `null` if it never was edited
	 */
	public get editedTimestamp() {
		return this[kEditedTimestamp];
	}

	/**
	 * The time the message was last edited at, or `null` if it never was edited
	 */
	public get editedAt() {
		const editedTimestamp = this.editedTimestamp;
		return editedTimestamp ? new Date(editedTimestamp) : null;
	}

	/**
	 * The flags of this message as a bit field
	 */
	public get flags() {
		const flags = this[kData].flags;
		return flags ? new MessageFlagsBitField(this[kData].flags as MessageFlags) : null;
	}

	/**
	 * The nonce used when sending this message.
	 *
	 * @remarks This is only present in MESSAGE_CREATE event, if a nonce was provided when sending
	 */
	public get nonce() {
		return this[kData].nonce;
	}

	/**
	 * Whether this message is pinned in its channel
	 */
	public get pinned() {
		return this[kData].pinned;
	}

	/**
	 * A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread
	 * It can be used to estimate the relative position of the message in a thread in company with `totalMessageSent` on parent thread
	 */
	public get position() {
		return this[kData].position;
	}

	/**
	 * Whether this message was a TTS message
	 */
	public get tts() {
		return this[kData].tts;
	}

	/**
	 * The type of message
	 */
	public get type() {
		return this[kData].type;
	}

	/**
	 * If the message is generated by a webhook, this is the webhook's id
	 */
	public get webhookId() {
		return this[kData].webhook_id;
	}

	/**
	 * {@inheritDoc Structure.toJSON}
	 */
	public override toJSON() {
		const clone = super.toJSON();
		if (this[kEditedTimestamp]) {
			clone.edited_timestamp = dateToDiscordISOTimestamp(new Date(this[kEditedTimestamp]));
		}

		const createdAt = this.createdAt;
		if (createdAt) {
			clone.timestamp = dateToDiscordISOTimestamp(createdAt);
		}

		return clone;
	}
}
